package com.ste.domain;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

import com.ste.server.Server;
import com.ste.server.command.Trade.OrderType;

public class OrderBook implements Runnable{
	public static final String FILLED = "35=8|11=%s|31=%f|32=%d|448=%s|39=2";
	public static final String PARTIALLYFILLED = "35=8|11=%s|31=%f|32=%d|448=%s|39=1";
	public static final String ASK = "35=8|11=%s|39=0";
	public static final String REJECT = "35=3|11=%s|373=9";
	private static final int QUEUE_CAPACITY = 10;
	private List<Order> buy = Collections.synchronizedList(new LinkedList<Order>());
	private List<Order> sell = Collections.synchronizedList(new LinkedList<Order>());

	private Float lastPrice;
	private Server server;

	private BlockingQueue<Order> orderQueue = new ArrayBlockingQueue<Order>(QUEUE_CAPACITY, true);

	public OrderBook(Server server) {
		this.server = server;
	}

	@Override
	public void run() {
		Order order = null;
		while(true){
			try {
				order = orderQueue.take();
				processingOrder(order);
			} catch (InterruptedException e) {
			}
		}
	};
	
	public void processingOrder(Order newOrder) {
		synchronized (buy) {
			synchronized (sell) {
				List<Order> listForMatching = null;
				List<Order> listForAdd = null;
				List<String> results = new ArrayList<String>();
				Comparator<Order> comparator = null;
				if (newOrder.getType() == OrderType.BUY) {
					listForMatching = sell;
					listForAdd = buy;
					comparator = getOrderBookIncComparator();
				} else {
					listForAdd = sell;
					listForMatching = buy;
					comparator = getOrderBookDecComparator();
				}
				for (Order existOrder : listForMatching) {
					if (existOrder.getOrderId().equals(newOrder.getOrderId())) {
						results.add(createMessage("reject", newOrder.getOrderId(), null, null, null));
					}
				}

				Iterator<Order> iterator = listForMatching.iterator();
				results.add(createMessage("ask", newOrder.getOrderId(), null, null, newOrder.getClientId()));
				while (iterator.hasNext()) {
					Order existingOrder = iterator.next();
					if (newOrder.getNumberOfShares() > 0 && (newOrder.getPrice() == null || existingOrder.getPrice() == null || comparator.compare(newOrder, existingOrder) >= 0)) {
						float price = getLastPrice(existingOrder, newOrder);
						int lots = 0;
						if (existingOrder.getNumberOfShares() <= newOrder.getNumberOfShares()) {
							lots = existingOrder.getNumberOfShares();
							newOrder.setNumberOfShares(newOrder.getNumberOfShares() - existingOrder.getNumberOfShares());
							results.add(createMessage("filled", existingOrder.getOrderId(), lots, price, newOrder.getClientId()));
							if (newOrder.getNumberOfShares() == 0) {
								results.add(createMessage("filled", newOrder.getOrderId(), lots, price, newOrder.getClientId()));
							} else {
								results.add(createMessage("partiallyFilled", newOrder.getOrderId(), lots, price, newOrder.getClientId()));
							}
							iterator.remove();
						} else {
							lots = newOrder.getNumberOfShares();
							existingOrder.setNumberOfShares(existingOrder.getNumberOfShares() - newOrder.getNumberOfShares());
							newOrder.setNumberOfShares(0);
							results.add(createMessage("partiallyFilled", existingOrder.getOrderId(), lots, price, newOrder.getClientId()));
							results.add(createMessage("filled", newOrder.getOrderId(), lots, price, newOrder.getClientId()));
						}
					}

				}
				if (newOrder.getNumberOfShares() > 0) {
					listForAdd.add(newOrder);
					Collections.sort(listForAdd, comparator);
				}
				notifyAllClients(results);
			}
		}
	}

	private String createMessage(String status, String orderId, Integer lots, Float price, String clientId) {
		switch (status) {
		case "filled":
			return String.format(FILLED, orderId, price, lots, clientId);
		case "partiallyFilled":
			return String.format(PARTIALLYFILLED, orderId, price, lots, clientId);
		case "ask":
			return String.format(ASK, orderId);
		case "reject":
			return String.format(REJECT, orderId);
		default:
			return null;
		}
	}

	public void notifyAllClients(List<String> messages) {
		synchronized (server.getClients()) {
			for (String message : messages)
				for (Client client : server.getClients()) {
					client.sendMessage(message);
				}
		}
	}
	public static Comparator<Order> getOrderBookIncComparator() {
		return new OrderComparator();
	}

	public Float getLastPrice(Order o, Order order) {
		if (o.getPrice() == null) {
			if (order.getPrice() != null) {
				setLastPrice(order.getPrice());
			}
		} else {
			setLastPrice(o.getPrice());
		}
		return lastPrice;
	}

	public void setLastPrice(Float lastPrice) {
		this.lastPrice = lastPrice;
	}

	public BlockingQueue<Order> getOrderQueue() {
		return orderQueue;
	}

	public void addOrder(Order order){
		try {
			orderQueue.put(order);
		} catch (InterruptedException e) {
		}
	}
	
	public static Comparator<Order> getOrderBookDecComparator() {
		return new OrderComparator() {
			@Override
			public int compare(Order o1, Order o2) {
				return super.compare(o2, o1);
			}
		};
	}

	static class OrderComparator implements Comparator<Order> {
		@Override
		public int compare(Order o1, Order o2) {
			if (o1.getPrice() == null) {
				if (o2.getPrice() == null) {
					return o1.getDate().compareTo(o2.getDate());
				} else {
					return 1;
				}
			} else {
				if (o2.getPrice() == null) {
					return -1;
				}
			}
			return o1.getPrice().compareTo(o2.getPrice());
		}
	}


}
